﻿using System;
using Loki.Utils;

namespace Loki.Events
{
    internal class WeakEventManager<TEventClass, TEventArgs> : IDisposable
        where TEventClass : class
        where TEventArgs : EventArgs
    {
        private WeakDictionary<TEventClass, WeakEventBridge<TEventClass, TEventArgs>> _SourceToBridgeTable;

        private Action<TEventClass, WeakEventBridge<TEventClass, TEventArgs>> _Mapper;

        private Action<TEventClass, WeakEventBridge<TEventClass, TEventArgs>> _UnMapper;

        public WeakEventManager(
            Action<TEventClass, WeakEventBridge<TEventClass, TEventArgs>> P_Mapper,
            Action<TEventClass, WeakEventBridge<TEventClass, TEventArgs>> P_UnMapper)
        {
            _SourceToBridgeTable = new WeakDictionary<TEventClass, WeakEventBridge<TEventClass, TEventArgs>>();
            _Mapper = P_Mapper;
            _UnMapper = P_UnMapper;
        }

        public void Register<TListener>(TEventClass P_Source, TListener P_Listener, Action<TListener, object, TEventArgs> P_Callback)
        {
            WeakEventBridge<TEventClass, TEventArgs> L_Bridge = GetBridgeForSource(P_Source);

            L_Bridge.AddListener(P_Listener, P_Callback);
        }

        private WeakEventBridge<TEventClass, TEventArgs> GetBridgeForSource(TEventClass P_Source)
        {
            WeakEventBridge<TEventClass, TEventArgs> L_Bridge;

            if (!_SourceToBridgeTable.TryGetValue(P_Source, out L_Bridge))
            {
                L_Bridge = AddNewPropertyBridgeToTable(P_Source);
            }

            // Can happen if the GC does it's magic
            if (L_Bridge == null)
            {
                L_Bridge = AddNewPropertyBridgeToTable(P_Source);
            }

            return L_Bridge;
        }

        private WeakEventBridge<TEventClass, TEventArgs> AddNewPropertyBridgeToTable(
            TEventClass P_Source)
        {
            WeakEventBridge<TEventClass, TEventArgs> L_Bridge = new WeakEventBridge<TEventClass, TEventArgs>(P_Source, _Mapper, _UnMapper);
            _SourceToBridgeTable[P_Source] = L_Bridge;
            return L_Bridge;
        }

        private void OnAllListenersForSourceUnsubscribed(TEventClass P_Source)
        {
            UnregisterSource(P_Source);
        }

        public void Unregister(TEventClass P_Source, object P_Listener)
        {
            WeakEventBridge<TEventClass, TEventArgs> L_Bridge;

            if (!_SourceToBridgeTable.TryGetValue(P_Source, out L_Bridge))
            {
                return;
            }

            if (L_Bridge == null)
            {
                _SourceToBridgeTable.Remove(P_Source);
                return;
            }

            L_Bridge.RemoveListener(P_Listener);
        }

        public void UnregisterSource(TEventClass P_Source)
        {
            if (_SourceToBridgeTable.ContainsKey(P_Source))
            {
                _SourceToBridgeTable.Remove(P_Source);
            }
        }

        public void RemoveCollectedEntries()
        {
            _SourceToBridgeTable.RemoveCollectedEntries();
        }

        #region IDisposable
        /// <summary>
        /// Releases all resources used by an instance of the <see cref="WeakNotifyChangedManager" /> class.
        /// </summary>
        /// <remarks>
        /// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
        /// finalization of the instance.
        /// </remarks>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~WeakEventManager()
        {
            Dispose(false);
        }

        /// <summary>
        /// Releases the unmanaged resources used by an instance of the <see cref="WeakNotifyChangedManager" /> class and optionally releases the managed resources.
        /// </summary>
        /// <param name="P_Disposing">Set <strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool P_Disposing)
        {
            if (P_Disposing)
            {
                _SourceToBridgeTable.Clear();
            }
        }
        #endregion
    }
}
